// Copyright (C) 2019 Toitware ApS. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the lib/LICENSE file.

import encoding.tison

/**
Mathematical functions and constants.
*/

/**
Pi.

The ratio of a circle's circumference to its diameter.
*/
PI ::= 3.14159265358979323846

/**
Euler's number.

The base of the natural logarithm.
*/
E ::= 2.71828182845904523536

/** Natural logarithm of 2. */
LN2 ::= 0.6931471805599453

/** Natural logarithm of 10. */
LN10 ::= 2.302585092994046

/** Base-2 logarithm of $E. */
LOG2E ::= 1.4426950408889634

/** Base-10 logarithm of $E. */
LOG10E ::= 0.4342944819032518

/** Square root of `1/2`. */
SQRT1-2 ::= 0.7071067811865476

/** Square root of 2. */
SQRT2 ::= 1.4142135623730951

/** Returns the sine of the $angle in radians. */
sin angle/num -> float:
  #primitive.math.sin

/** Returns the cosine of the $angle in radians. */
cos angle/num -> float:
  #primitive.math.cos

/** Returns the tangent of the $angle in radians. */
tan angle/num -> float:
  #primitive.math.tan

/** Returns the hyperbolic sine of the hyperbolic angle $x. */
sinh x/num -> float:
  #primitive.math.sinh

/** Returns the hyperbolic cosine of the hyperbolic angle $x. */
cosh x/num -> float:
  #primitive.math.cosh

/** Returns the hyperbolic tangent of the hyperbolic angle $x. */
tanh x/num -> float:
  #primitive.math.tanh

/** Returns (in radians) the arcsine of the given $x. */
asin x/num -> float:
  #primitive.math.asin

/** Returns (in radians) the arccosine of the given $x. */
acos x/num -> float:
  #primitive.math.acos

/**
Returns the arctangent of the given $x.
The result (in radians) will be in the range -pi/2 to pi/2 and have the same sign as $x.
*/
atan x/num -> float:
  #primitive.math.atan

/** Returns the two argument arctangent of $x and $y. */
atan2 x/num y/num -> float:
  #primitive.math.atan2

/**
Returns the square root of the given $x.
For negative $x, NaN ($float.NAN) is returned.
*/
sqrt x/num -> float:
  #primitive.math.sqrt

/** Returns $x to the power of $y. */
pow x/num y/num -> float:
  #primitive.math.pow

/** Returns the exponential of the given $x, i.e. Euler's number to the power of $x. */
exp x/num -> float:
  #primitive.math.exp

/**
Returns the natural logarithm of the given $x.
For negative $x, NaN ($float.NAN) is returned.
*/
log x/num -> float:
  #primitive.math.log

/**
Returns, approximately, the $n base logarithm of the given $x.
For negative $x or $n, NaN ($float.NAN) is returned.
*/
log x/num n/num -> float:
  return (log x) / (log n)

/**
A 3D point with coordinates represented as floats.
Points are immutable.
*/
class Point3f:
  /** The x-coordinate of the point. */
  x/float ::= ?
  /** The y-coordinate of the point. */
  y/float ::= ?
  /** The z-coordinate of the point. */
  z/float ::= ?

  /** Creates a point from $x, $y, and $z. */
  constructor x/num y/num z/num:
    this.x = x.to-float
    this.y = y.to-float
    this.z = z.to-float

  /**
  Deserializes $bytes into a point instance.
  The serialization format is unspecified, but bytes generated by
    $to-byte-array can be deserialized.
  */
  constructor.deserialize bytes:
    values ::= tison.decode bytes
    return Point3f values[0] values[1] values[2]

  /**
  Converts this instance to a byte array which can be deserialized with
    $Point3f.deserialize.
  The serialization format is not stable.
  */
  to-byte-array -> ByteArray:
    return tison.encode [x, y ,z]

  /** See $super. */
  stringify -> string:
    return "x=$(%7.3f x) y=$(%7.3f y) z=$(%7.3f z)"

  /**
  Returns a copy of this instance with absolute coordinates
    (that is (|$x|, |$y|, |$z|)).
  */
  abs -> Point3f:
    return Point3f x.abs y.abs z.abs

  /** Computes the distance from (0, 0, 0) to this instance. */
  length -> float:
    return sqrt
      x * x + y * y + z * z

  /**
  Adds this instance to the $other and returns the result
    (that is (x + other.x, y + other.y, z + other.z)).
  */
  operator + other/Point3f -> Point3f:
    return Point3f
      x + other.x
      y + other.y
      z + other.z

  /**
  Returns a copy of this instance with negated coordinates
    (that is (-x, -y, -z)).
  */
  operator - -> Point3f:
    return Point3f -x -y -z

  /**
  Subtracts this instance from the $other and returns the result
    (that is (x - other.x, y - other.y, z - other.z)).
  */
  operator - other/Point3f -> Point3f:
    return Point3f
      x - other.x
      y - other.y
      z - other.z

  /**
  Multiplies this instance with the $other and returns the result.

  The $other can be a scalar or another point.
  If the $other is a point, then multiplication is done per coordinate
    (that is (x * other.x, y * other.y, z * other.z)).
  If the $other is a scalar, then each coordinate is multiplied
    (that is (x * other, y * other, z * other)).
  */
  operator * other -> Point3f:
    if other is num:
      return Point3f
        x * other
        y * other
        z * other
    if other is Point3f:
      return Point3f
        x * other.x
        y * other.y
        z * other.z
    throw "UNSUPPORTED_TYPE"

  /**
  Divides this instance with the $other and returns the result.

  The $other can be a point or a scalar.
  If the $other is a point, then the division is done per coordinate
    (that is (x / other.x, y / other.y, z / other.z)).
  If the $other is a scalar, then each coordinate is divided by the scalar
    (that is (x / other, y / other, z / other)).
  */
  operator / other -> Point3f:
    if other is num:
      return Point3f
        x / other
        y / other
        z / other
    if other is Point3f:
      return Point3f
        x / other.x
        y / other.y
        z / other.z
    throw "UNSUPPORTED_TYPE"
